package cn.qingyun.gis.modules.sftp.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.qingyun.gis.exception.CommonException;
import cn.qingyun.gis.modules.express.constant.RedisContant;
import cn.qingyun.gis.modules.express.entity.ExpressRegion;
import cn.qingyun.gis.modules.express.service.IExpressRegionService;
import cn.qingyun.gis.modules.init.system.entity.SysLog;
import cn.qingyun.gis.modules.init.system.mapper.SysLogMapper;
import cn.qingyun.gis.modules.init.utils.CsvTool;
import cn.qingyun.gis.modules.sftp.config.SftpProperties;
import cn.qingyun.gis.modules.sftp.service.IFileSystemService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Lists;
import com.jcraft.jsch.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class FileSystemServiceImpl implements IFileSystemService {

    @Resource
    private SftpProperties config;

    @Resource
    private IExpressRegionService iExpressRegionService;

    @Resource
    private SysLogMapper sysLogMapper;
    @Resource
    private RedissonClient redissonClient;

    @Value("${qingyun.sftp.cachePath:/tmp/}")
    private String cachePath; // 本地缓存路径


    // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
    private static final String SESSION_CONFIG_STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";

    public FileSystemServiceImpl(SftpProperties config) {
        this.config = config;
    }


    @Override
    public boolean uploadFile(String targetPath, InputStream inputStream) throws Exception {
        ChannelSftp channel = this.createSftp();
        try {
            channel.cd(config.getRoot()); // 进入upload 如果不进入根目录下 无法进行根目录下子路径创建
            log.debug("Change path to {}", config.getRoot());

            int index = targetPath.lastIndexOf("/");
            String fileDir = targetPath.substring(0, index);
            String fileName = targetPath.substring(index + 1);
            boolean dirs = this.createDirs(fileDir, channel); //在根目录下创建子路径
            if (!dirs) {
                log.error("Remote path error. path:{}", targetPath);
                throw new CommonException("Upload File failure");
            }
            channel.put(inputStream, fileName);
            return true;
        } catch (Exception e) {
            log.error("Upload file failure. TargetPath: {}", targetPath, e);
            throw new CommonException("Upload File failure");
        } finally {
            this.disconnect(channel);
        }

    }


    @Override
    public boolean uploadFiles(String targetPath, MultipartFile file) throws Exception {
        return this.uploadFile(targetPath, file.getInputStream());
    }

    @SneakyThrows
    @Override
    public File downloadFile(String targetPath, String cachePath) {
        log.debug("服务配置 {}", config);
        ChannelSftp sftp = this.createSftp();
        OutputStream outputStream = null;
        try {
            sftp.cd(config.getRoot());
            log.debug("Change path to {}", config.getRoot());
            // 定义缓冲路径
            File file = new File(cachePath + (targetPath.substring(targetPath.lastIndexOf("/") + 1)));
            // 调用 sftp 服务，将文件获取至缓冲路径中
            outputStream = Files.newOutputStream(file.toPath());
            sftp.get(targetPath, outputStream);
            // 进行分隔符转换（\t -> ,）
            CsvTool.transferTab(file);
            // 追加表头
            CsvTool.addHead(file, "日期,编码,父节点编码,名称,WKT,层级,状态,面积,区域负责人名称,区域负责人工号,网格负责人电话,hr员工编码,网格类型,业务类型,政企网格类型,网格看管模式,全国统一编码");
            log.debug("export cachePath to {} succeed", file.toPath());
            return file;
        } catch (Exception e) {
            throw new CommonException(e);
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            this.disconnect(sftp);
        }
    }

    @Override
    public void downloadRegion() {

        RLock rlock = redissonClient.getLock(RedisContant.SCHEDULED_JOB_LOCK);
        SysLog sysLog = new SysLog(); // 创建日志对象
        String fileName = "W08130" + DateUtil.format(DateUtil.yesterday(), "yyyyMMdd") + "000000.csv";

        List<ExpressRegion> data = Lists.newArrayList();
        String message = null; // 日志内容
        long start = System.currentTimeMillis();
        try {
            boolean lockRes = rlock.tryLock(10, TimeUnit.SECONDS);
            if (lockRes) {
                File file = this.downloadFile(config.getRoot() + "/" + fileName, cachePath);
                if (ObjectUtil.isNotEmpty(file)) {
                    data = CsvTool.transferFile(file, ExpressRegion.class);
                    if (!data.isEmpty()) {
                        //更新库-业务区划表
                        iExpressRegionService.remove(new QueryWrapper<>());
                        if(iExpressRegionService.saveBatch(data)){
                            // 若库中数据已经更新，进行84->02
                            iExpressRegionService.wkt84To02();
                            // 进行边界修复
                            iExpressRegionService.repairInvalidBoundary();
                        }

                        message = String.format("SFTP获取业务区划%s,更新业务区划表成功", fileName);
                        log.debug("使用按钮通过SFTP获取最新业务区划数据更新成功");
                    } else {
                        log.debug("添加表头失败。");
                    }

                }
            }
        } catch (Exception e) {
            message = String.format("使用按钮通过SFTP获取业务区划%s,更新业务区划表失败", fileName);
            log.error("使用SFTP获取最新业务区划数据失败！");
        } finally {
            rlock.unlock();
            long finish = System.currentTimeMillis();
            sysLog.setLogContent(message);
            sysLog.setCreateTime(DateUtil.date());
            sysLog.setLogType(3); //定时任务日志类型
            sysLog.setCreateBy("system");
            sysLog.setCostTime(finish - start);
            sysLog.setUserid("SFTP服务");
            sysLogMapper.insert(sysLog);
        }
    }

    private boolean createDirs(String dirPath, ChannelSftp sftp) {
        if (dirPath != null && !dirPath.isEmpty()
                && sftp != null) {
            String[] dirs = Arrays.stream(dirPath.split("/"))
                    .filter(StringUtils::isNotBlank)
                    .toArray(String[]::new);

            for (String dir : dirs) {
                try {
                    sftp.cd(dir);
                    log.debug("Change directory {}", dir);
                } catch (Exception e) {
                    try {
                        sftp.mkdir(dir);
                        log.debug("Create directory {}", dir);
                    } catch (SftpException e1) {
                        log.error("Create directory failure, directory:{}", dir, e1);
                        e1.printStackTrace();
                    }
                    try {
                        sftp.cd(dir);
                        log.debug("Change directory {}", dir);
                    } catch (SftpException e1) {
                        log.error("Change directory failure, directory:{}", dir, e1);
                        e1.printStackTrace();
                    }
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 创建SFTP连接
     *
     * @return
     * @throws Exception
     */
    private ChannelSftp createSftp() throws Exception {
        JSch jsch = new JSch();
        log.debug("Try to connect sftp[" + config.getUsername() + "@" + config.getHost() + "], use password[" + config.getPassword() + "]");

        Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
        session.setPassword(config.getPassword());
        session.connect(config.getSessionConnectTimeout());

        log.debug("Session connected to {}.", config.getHost());

        Channel channel = session.openChannel(config.getProtocol());
        channel.connect(config.getChannelConnectedTimeout());

        log.debug("Channel created to {}.", config.getHost());

        return (ChannelSftp) channel;
    }

    /**
     * 创建session
     *
     * @param jsch
     * @param host
     * @param username
     * @param port
     * @return
     * @throws Exception
     */
    private Session createSession(JSch jsch, String host, String username, Integer port) throws Exception {
        Session session;

        if (port <= 0) {
            session = jsch.getSession(username, host);
        } else {
            session = jsch.getSession(username, host, port);
        }

        if (session == null) {
            throw new Exception(host + " session is null");
        }

        session.setConfig(SESSION_CONFIG_STRICT_HOST_KEY_CHECKING, config.getSessionStrictHostKeyChecking());
        return session;
    }

    /**
     * 关闭连接
     *
     * @param sftp
     */
    private void disconnect(ChannelSftp sftp) {
        try {
            if (sftp != null) {
                if (sftp.isConnected()) {
                    sftp.disconnect();
                } else if (sftp.isClosed()) {
                    log.debug("sftp is closed already");
                }
                if (null != sftp.getSession()) {
                    sftp.getSession().disconnect();
                }
            }
        } catch (JSchException e) {
            e.printStackTrace();
        }
    }
}
